// =======================================================================
// 
//       Filename:  processor.cpp
// 
//    Description:  
// 
//        Version:  1.0
//        Created:  09.04.2010 22:42:26
//       Revision:  none
//       Compiler:  g++
// 
//         Author:  Dominik 'domachine' Burgdörfer (-), dominik.burgdoerfer@googlemail.com
//        Company:  -
// 
// =======================================================================

#include "processor.hpp"
#include <fstream>
#include <cstdlib>
#include <boost/regex.hpp>
#include "utils/popen2.hpp"
#include "template/file.hpp"
#include "template/varelement.hpp"
#include "types/string.hpp"
#include "types/integer.hpp"
#include "types/float.hpp"
#include <boost/foreach.hpp>
#include <boost/any.hpp>
#include <algorithm>

#define foreach BOOST_FOREACH

using namespace std;
using namespace boost;

namespace {
    using namespace webplodder;

    bool compareNodes(const navigation::Node& node1,
                     const navigation::Node& node2)
    {
        return (node1.pos() < node2.pos());
    }
}

namespace webplodder {
    using namespace types;

    Processor::Processor(map<string,string>&
                         scriptConfig)
        : mPage(0), mFirstRun(true), mPlugins(),
          mScriptConfig(&scriptConfig)
    {
    }

    Processor::~Processor() {
    }

    void Processor::process(const Template::File& file,
                            Page& page)
    {
        ifstream inStream(file.sourcePath().string().c_str());
        mPage = &page;
        mTemplateSource = file.sourcePath().filename();

        if(!inStream.good())
            throw runtime_error("unable to read template file");

        CIterator cIter(inStream);
        ofstream out(page.outputPath().string().c_str());

        if(!out.good())
            throw runtime_error("unable to create file: " +
                                     page.outputPath().string());

        // sort navigation
        sort(mPage->navigation().begin(),
                mPage->navigation().end(), compareNodes);

        foreach(shared_ptr<Template::Element> current, file) {
            if(current->type() == Object::TEMPLATE_ELEMENT) {
                if(current->elementType() == Template::Element::TEXT) {
                    shared_ptr<Template::TextElement> textElement =
                        dynamic_pointer_cast<Template::TextElement>(
                                current);
                    processText(cIter, *textElement, out);
                }
                else if(current->elementType() == 
                        Template::Element::COMMAND) {
                    shared_ptr<Template::CommandElement> cmd =
                        dynamic_pointer_cast<Template::CommandElement>(
                                current);
                    processCmd(cIter, *cmd, out);
                }
                else if(current->elementType() == 
                        Template::Element::SCRIPT) {
                    shared_ptr<Template::ScriptElement> script =
                        dynamic_pointer_cast<Template::ScriptElement>(
                                current);
                    execScript(cIter, *script, out);
                }
                else if(current->elementType() == 
                        Template::Element::VAR) {
                    shared_ptr<Template::VarElement> var =
                        dynamic_pointer_cast<Template::VarElement>(
                                current);

                    if(mPage->find(var->name()) == mPage->end())
                        cerr << mTemplateSource << ":"
                            << var->line() 
                            << ": warning: undefined variable: "
                            << var->name() << endl;
                    else
                        out << (*mPage)[var->name()];
                }
            }
        }

        mFirstRun = false;
        out.close();
    }

    void Processor::installPlugin(const shared_ptr<Plugin>& plugin)
    {
        mPlugins.push_back(plugin);
    }

    void Processor::execSetCmd(const vector<any>& args,
                               int line)
    {
        if(args.size() > 2 || args.size() < 2) {
            if(mFirstRun)
                cerr << mTemplateSource << ":"
                    << line
                    << ": warning: invalid number of arguments to set"
                    << endl;
        }
        else {
            regex cmdregex("[a-zA-Z_0-9]+");

            if(args[0].type() != typeid(string)) {
                if(mFirstRun)
                    cerr << mTemplateSource << ":"
                        << line
                        << ": warning: set: invalid type of first "
                        << "argument"
                        << endl;
                return;
            }

            if(args[1].type() != typeid(string)) {
                if(mFirstRun)
                    cerr << mTemplateSource << ":"
                        << line
                        << ": warning: set: variables can hold strings only"
                        << endl;
                return;
            }

            string var = any_cast<string>(args[0]);
            if(!regex_match(var.c_str(), cmdregex)) {
                if(mFirstRun)
                    cerr << mTemplateSource << ":"
                        << line
                        << ": warning: invalid variable name: `"
                        << var << "'"
                        << endl;
            }
            else {
                (*mPage)[var] = any_cast<string>(args[1]);
            }
        }
    }

    void Processor::execNavigationCmd(const vector<any>& args,
                                      int line,
                                      ostream& output)
    {
        if(args.size() > 1 || args.size() < 1) {
            if(mFirstRun)
                cerr << mTemplateSource << ":"
                    << line
                    << ": warning: invalid number of arguments "
                    << "to navigation"
                    << endl;
        }
        else {

            if(args[0].type() != typeid(string)) {
                if(mFirstRun)
                    cerr << mTemplateSource << ":"
                        << ": warning: navigation: "
                        << "invalid argument type"
                        << endl;
            }
            else {
                string pattern = any_cast<string>(args[0]);
                map<string,string>::iterator it;
                regex titleRegex("\\{title\\}");
                regex hrefRegex("\\{href\\}");
                regex activeRegex("\\{active\\}");

                cerr << mPage->navigation().size() << endl;
                foreach(navigation::Node node, mPage->navigation())
                {
                    cerr << "node: " << node.title() << endl;
                    string impl;

                    impl = regex_replace(pattern,
                            titleRegex,
                            node.title());
                    impl = regex_replace(impl,
                            hrefRegex,
                            node.href());

                    if(node.title() == (*mPage)["title"])
                        impl = regex_replace(impl,
                                activeRegex,
                                "id='active'");
                    else
                        impl = regex_replace(impl,
                                activeRegex,
                                "");
                    output << impl;
                }
            }
        }
    }

    void Processor::execScript(CIterator& input, 
                               Template::ScriptElement& script,
                               ostream& output)
    {
        if((*mScriptConfig)[script.specification()].empty()) {
            if(mFirstRun)
                cerr << mTemplateSource << ":"
                      << script.line() 
                      << ": warning: no rule defined for: "
                      << script.specification() << endl;
        }
        else {
            Page::iterator pageIter;

            for(pageIter = mPage->begin(); pageIter != mPage->end();
                    pageIter++)
            {
                string envVar("WP_");
                envVar.append(pageIter->first);

                setenv(envVar.c_str(), pageIter->second.c_str(), 1);
            }

            string scriptSpec = (*mScriptConfig)[
                                            script.specification()];

            int outfp, infp;
            pid_t ret = utils::popen2(scriptSpec.c_str(), &infp, &outfp);

            if (ret >= 0)
            {
                char ch;
                script.setInput(input);
                while(script >> ch) {
                    write(infp, &ch, 1);
                }
                close(infp);

                while(read(outfp, &ch, 1) > 0)
                    output << ch;

                close(outfp);
            }

            for(pageIter = mPage->begin(); pageIter != mPage->end();
                    pageIter++)
            {
                string envVar("WP_");
                envVar.append(pageIter->first);

                unsetenv(envVar.c_str());
            }
        }
    }

    void Processor::processCmd(CIterator& input,
                               Template::CommandElement& cmd,
                               ostream& output)
    {
        string cmdName = cmd.name();

        if(cmdName == "title")
            output << (*mPage)["title"];
        else if(cmdName == "content")
            output << (*mPage)["content"];
        else {

            int calledPlugins = 0;

            vector<any> args;

            foreach(shared_ptr<Object> current, 
                    cmd.parameters()) {
                string currentArg;
                char tmp;

                if(current->type() == Object::TEMPLATE_ELEMENT) {
                    shared_ptr<Template::Element> currentElement =
                        dynamic_pointer_cast<Template::Element>(current);

                    if(currentElement->elementType() == 
                            Template::Element::VAR) {
                        shared_ptr<Template::VarElement> var = 
                            dynamic_pointer_cast<Template::VarElement>(
                                    currentElement);
                        currentArg = (*mPage)[var->name()];
                    }
                    else if(currentElement->elementType() == 
                            Template::Element::SCRIPT) {
                        ostringstream stream;
                        shared_ptr<Template::ScriptElement> script =
                            dynamic_pointer_cast<
                                Template::ScriptElement>(
                                    currentElement);
                        execScript(input, *script, stream);
                        currentArg = stream.str();
                    }
                    else if(currentElement->elementType() == 
                            Template::Element::COMMAND)
                    {
                        ostringstream stream;
                        shared_ptr<Template::CommandElement> command =
                            dynamic_pointer_cast<
                                Template::CommandElement>(
                                    currentElement);
                        processCmd(input, *command, stream);
                        currentArg = stream.str();
                    }
                    else
                        while((*currentElement) >> tmp)
                            currentArg.push_back(tmp);

                    args.push_back(currentArg);
                }
                else if(current->type() == Object::STRING) {
                    shared_ptr<String> str =
                        dynamic_pointer_cast<String>(current);

                    shared_ptr<string> s =
                        static_pointer_cast<string>(str);
                    args.push_back(*s);
                }
                else if(current->type() == Object::INTEGER) {
                    shared_ptr<Integer> integer =
                        dynamic_pointer_cast<Integer>(current);

                    int i =
                        static_cast<int>(*integer);
                    args.push_back(i);
                }
                else if(current->type() == Object::FLOAT) {
                    shared_ptr<Float> f =
                        dynamic_pointer_cast<Float>(current);

                    float d =
                        static_cast<float>(*f);
                    args.push_back(d);
                }
            }

            if(cmdName == "set") {
                execSetCmd(args, cmd.line());
            }
            else if(cmdName == "navigation") {
                execNavigationCmd(args, cmd.line(),
                                  output);
            }
            else {
                foreach(shared_ptr<Plugin> plugin, mPlugins) {
                    if(plugin->name() == cmdName) {
                        plugin->call(*mPage, output, args);
                        calledPlugins++;
                    }
                }

                if(calledPlugins == 0 && mFirstRun == true)
                    cerr << mTemplateSource << ":"
                        << cmd.line()
                        << ": warning: unknown command: "
                        << cmdName << endl;
            }
        }
    }

    void Processor::processText(CIterator& input,
                                Template::TextElement& text,
                                ostream& output)
    {
        char ch;
        text.setInput(input);
        while(text >> ch)
            output << ch;
    }
}
